#ifndef __M_SESSION_H__
#define __M_SESSION_H__
#include "util.hpp"
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

// 定义用户状态:登录 未登录
typedef enum
{
    UNLOHIN,
    LOGIN
} ss_statu;

class session
{
private:
    uint64_t _ssid;            // 标识符
    uint64_t _uid;             // session对应的用户id
    ss_statu _statu;           // 用户状态:未登录 已登录
    wsserver_t::timer_ptr _tp; // session关联的定时器
public:
    session(uint64_t ssid)
        : _ssid(ssid) { DLOG("session %p被创建", this); }

    ~session() { DLOG("session %p被释放", this); }

    // 设置状态
    void set_statu(ss_statu statu) { _statu = statu; }

    // 设置用户id
    void set_user(uint64_t uid) { _uid = uid; }

    // 获取ssid
    uint64_t ssid() { return _ssid; }

    // 获取用户id
    uint64_t get_user_id() { return _uid; }

    // 判断用户是否登录
    bool is_login() { return _statu == LOGIN; }

    // 设置定时器
    void set_timer(const wsserver_t::timer_ptr &tp) { _tp = tp; }

    // 获取定时器
    wsserver_t::timer_ptr &get_timer() { return _tp; }
};

// 定义session的智能指针
using session_ptr = std::shared_ptr<session>;

// 定义删除时间
#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1

class session_manager
{
private:
    uint64_t _next_ssid;                                // 下一个ssid
    std::mutex _mutex;                                  // 互斥锁
    std::unordered_map<uint64_t, session_ptr> _session; // session id 与session的关联关系
    wsserver_t *_server;                                // 服务器句柄
public:
    session_manager(wsserver_t *server)
        : _next_ssid(1), _server(server)
    {
        DLOG("session管理器初始化完成");
    }
    ~session_manager()
    {
        DLOG("session管理器销毁完成");
    }
    
public:
    // 创建session对象
    session_ptr create_session(uint64_t uid, ss_statu statu)
    {
        std::unique_lock<std::mutex> lock(_mutex);

        session_ptr ssp(new session(_next_ssid));

        // 设置用户状态和用户信息
        ssp->set_statu(statu);
        ssp->set_user(uid);

        // 将关联关系插入到哈希表中
        _session.insert(std::make_pair(_next_ssid, ssp));

        _next_ssid++;
        return ssp;
    }

    // 通过ssid获取session信息
    session_ptr get_session_by_ssid(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(ssid);
        if (it == _session.end())
        {
            return session_ptr();
        }

        return it->second;
    }

    // 添加定时器
    void append_session(const session_ptr &ssp)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.insert(std::make_pair(ssp->ssid(), ssp));
    }

    // 销毁session
    void remove_session(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.erase(ssid);
    }

    // 为session设置过期时间
    void set_session_expire_time(uint64_t ssid, int ms)
    {
        // 依赖于websocketpp的定时器来完成session生命周期的管理
        // 在http通信的时候(登录,注册)session应该具备生命周期，指定时间无通信后删除
        // 在客户端建立websocket长连接之后，session应该是永久存在的
        // 登录之后，创建session，session需要在指定时间无通信后删除
        // 但是进入游戏大厅，或者游戏房间，这个session就应该永久存在
        // 等到退出游戏大厅，或者游戏房间，这个session应该被重新设置为临时，在长时间无通信后被删除

        // 根据ssp获取session对象的智能指针对象
        session_ptr ssp = get_session_by_ssid(ssid);
        if (ssp.get() == nullptr)
        {
            return;
        }

        // 获取定时器
        wsserver_t::timer_ptr tp = ssp->get_timer();
        // 1.在session永久存在的情况下，设置永久存在
        if (tp.get() == nullptr && ms == SESSION_FOREVER)
        {
            return;
        }

        // 2.在session永久存在的情况下，设置指定时间之后被删除的定时任务
        else if (tp.get() == nullptr && ms != SESSION_FOREVER)
        {
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms,
                std::bind(&session_manager::remove_session, this, ssid));
            ssp->set_timer(tmp_tp);
        }

        // 3.在session设置了定时删除的情况下，将session设置为永久存在
        else if (tp.get() != nullptr && ms == SESSION_FOREVER)
        {
            // 删除定时任务---stready_timer删除定时任务会导致任务会立即执行
            // 因为这个取消取消定时任务并不是立即取消的
            tp->cancel();
            // 因此重新给session管理器中，添加一个session信息，且添加的时候需要使用定时器，而不是立即添加
            // 将session关联的定时器置为空
            ssp->set_timer(wsserver_t::timer_ptr());
            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));
        }

        // 4.在session设置了定时删除的情况下，将session重置删除时间
        else if (tp.get() != nullptr && ms != SESSION_FOREVER)
        {
            // 因为这个取消取消定时任务并不是立即取消的
            tp->cancel();
            // 将session关联的定时器置为空
            ssp->set_timer(wsserver_t::timer_ptr());
            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));

            // 重新给session添加定时销毁任务
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms,
                std::bind(&session_manager::remove_session, this, ssp->ssid()));

            // 重新设置session关联的定时器
            ssp->set_timer(tmp_tp);
        }
    }
};

#endif